package com.sl.core.engine.scripts;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONNull;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.sl.core.utils.JsonUtil;
import groovy.lang.*;
import groovy.transform.ThreadInterrupt;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
import org.kohsuke.groovy.sandbox.SandboxTransformer;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class GroovyScriptExecutor {
    private static final Cache<String, Class<Script>> SCRIPT_CLASS_CACHE = CacheBuilder.newBuilder()
            .expireAfterAccess(1, TimeUnit.DAYS)
            .initialCapacity(1000).maximumSize(30000).build();
    public static final String MAIN_METHOD_NAME = "main";

    public static final GroovyScriptExecutor INSTANCE = new GroovyScriptExecutor();

    /**
     * 创建一个groovyShell
     *
     * @return
     */
    private GroovyClassLoader createGroovyClassLoader() {
        // 自定义配置
        CompilerConfiguration config = new CompilerConfiguration();
        config.setParameters(true);
        // 添加线程中断拦截器，可拦截循环体（for,while）、方法和闭包的首指令
        config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class));
        // 添加线程中断拦截器，可中断超时线程
        config.addCompilationCustomizers(new SandboxTransformer());
        new GroovyNotSupportInterceptor().register();
        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);
        // 注册方法拦截
        return groovyClassLoader;
    }

    @SneakyThrows
    public Class<Script> compileScriptClass(String scriptStr) {
        //算MD5
        String sec = SecureUtil.md5(scriptStr);
//        log.error("groovy脚本，计算MD5耗时{}ms", endTime - startTime);
        Class<Script> scriptClass = SCRIPT_CLASS_CACHE.get(sec, () -> {
            // 缓存脚本，提高执行性能
            //每一个脚本classLoader应该独立 这样才不至于会串门
            String sc = scriptStr.replaceAll("(public)?\\s*(static)?\\s+class\\s+([\\w\\.\\_\\-]+)\\s*\\{", "\n class $3\\{");

            //这里导入包

            GroovyClassLoader groovyClassLoader = createGroovyClassLoader();
            GroovyCodeSource gcs = new GroovyCodeSource(sc, "Script_" + sec + ".groovy", "/groovy/script");
            gcs.setCachable(true);
            Class<Script> script = groovyClassLoader.parseClass(gcs, true);
            return script;
        });
        return scriptClass;
    }

    public Method getRunMethod(String str) {
        if (StrUtil.isBlank(str)) {
            return null;
        }
        Class<Script> scriptClass = this.compileScriptClass(str);
        Method[] methods = ReflectUtil.getMethods(scriptClass);
        return Arrays.stream(methods).filter(v -> {
            boolean equals = StrUtil.equals(v.getName(), MAIN_METHOD_NAME);

            Parameter[] parameters = v.getParameters();
            if (parameters.length == 1) {
                Parameter parameter = parameters[0];
                if (parameter.getType().isArray()) {
                    return false;
                }
            }
            return equals;

        }).findFirst().orElse(null);
    }


    /**
     * 变量绑定
     *
     * @return
     */
    private Binding doBinding() {
        Binding binding = new Binding();
        binding.setVariable("app", SpringUtil.getApplicationContext());
        binding.setVariable("log", log);
        return binding;
    }

    private Object clone(Object input) {
        if (input == null) {
            return null;
        }
        if (input instanceof ArrayList) {
            return ((ArrayList<?>) input).clone();
        }
        if (input instanceof HashMap) {
            return ((HashMap<?, ?>) input).clone();
        }
        if (input instanceof LinkedHashMap) {
            return ((LinkedHashMap<?, ?>) input).clone();
        }
        return input;
    }

    public Object executeScript(String scriptStr, Object... args) {
        return this.executeScript(scriptStr, MAIN_METHOD_NAME, false, args);
    }


    public Object executeScriptAndFormat(String scriptStr, Object... args) {
        return this.executeScript(scriptStr, MAIN_METHOD_NAME, true, args);
    }

    public Object executeScript(String scriptStr, String methodName, Boolean formatFlag, Object... args) {
        if (StrUtil.isBlank(scriptStr)) {
            return null;
        }
        //脚本提前处理 兼容老版本
        //定义类上增加static,解决定义类使用静态函数创建对象问题
        Class<Script> scriptClass = this.compileScriptClass(scriptStr);

        Binding binding = doBinding();
//         startTime = System.currentTimeMillis();
        Script script = InvokerHelper.createScript(scriptClass, binding);
        Object result = InvokerHelper.invokeMethod(script, methodName, args);
//         endTime = System.currentTimeMillis();
        if (ObjectUtil.isNull(formatFlag) || BooleanUtil.isFalse(formatFlag)) {
            return result;
        }
//        log.error("groovy脚本，执行{}ms", endTime - startTime);
        return formatter(result);
    }

    private Object formatter(Object result) {
        if (result == null) {
            return null;
        }
        if (result instanceof GroovyObject) {
            return objConvert2Map(result);
        } else if (result instanceof Collection) {
            return ((Collection<?>) result).stream().map(this::formatter).collect(Collectors.toList());
        } else if (result instanceof Map) {
            Map<Object, Object> m = (Map<Object, Object>) result;
            HashMap<String, Object> newMap = new LinkedHashMap<>(m.size());
            for (Map.Entry<Object, Object> entry : m.entrySet()) {
                if (!(entry.getKey() instanceof String)) {
                    throw new RuntimeException("map的key只能是string类型");
                }
                newMap.put((String) entry.getKey(), formatter(entry.getValue()));
            }
            return newMap;
            //如果是数组的话转成list
        } else if (result.getClass().isArray()) {
            return Arrays.stream(((Object[]) result)).collect(Collectors.toList());
        } else if (result instanceof String) {
            return result;
        } else if (result instanceof Integer) {
            return result;
        } else if (result instanceof Boolean) {
            return result;
        } else if (result instanceof Long) {
            return result;
        } else if (result instanceof Float) {
            return result;
        } else if (result instanceof Double) {
            return result;
        } else if (result instanceof Byte) {
            return result;
        } else if (result instanceof Character) {
            return result;
        } else if (result instanceof Short) {
            return result;
        } else if (result instanceof Date) {
            return result;
        } else if (result instanceof LocalDateTime) {
            LocalDateTime k = (LocalDateTime) result;
            //这里需要转换一下
            return DateUtil.parse(DateUtil.format(k, DatePattern.NORM_DATETIME_MS_PATTERN), DatePattern.NORM_DATETIME_MS_PATTERN);
        } else if (result instanceof Number) {
            return result;
        } else if (result instanceof JSONNull) {
            return null;
        } else {
            return result;
        }
    }

    private Map<String, Object> objConvert2Map(Object result) {
        //如果是对象 但声明的首字母又是大写这种情况序列化后会把对象字段首字母转为小写 严格按照驼峰
        Map<String, Object> hm = JsonUtil.toMap(result);
        return hm;
    }


    public static class GroovyNotSupportInterceptor extends GroovyInterceptor {

        public static final List<String> defaultMethodBlacklist = Arrays.asList("wait", "notify", "notifyAll", "finalize");

        /**
         * 静态方法拦截
         *
         * @param invoker
         * @param receiver
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
            if (receiver == System.class && "exit".equals(method)) {
                // System.exit(0)
                throw new SecurityException("No call on System.exit() please");
            } else if (receiver == Runtime.class) {
                // 通过Java的Runtime.getRuntime().exec()方法执行shell, 操作服务器…
                throw new SecurityException("No call on RunTime please");
            } else if (receiver == Class.class && "forName".equals(method)) {
                // Class.forName
            }
            return super.onStaticCall(invoker, receiver, method, args);
        }

        /**
         * 普通方法拦截
         *
         * @param invoker
         * @param receiver
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object onMethodCall(GroovyInterceptor.Invoker invoker, Object receiver, String method, Object... args) throws Throwable {
            if (defaultMethodBlacklist.contains(method)) {
                // 方法列表黑名单
                throw new SecurityException("Not support method: " + method);
            }
            return super.onMethodCall(invoker, receiver, method, args);
        }

        @Override
        public Object onGetProperty(Invoker invoker, Object receiver, String property) throws Throwable {
//            if ("class".contains(property)) {
//                throw new SecurityException("Not support clz.class");
//            }
            return super.onGetProperty(invoker, receiver, property);
        }
    }
}
